package com.mathdroid.gui.main.abilities;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import com.mathdroid.gui.ImageLoader;
import com.mathdroid.gui.componentlist.ComponentList;
import com.mathdroid.gui.main.DroidComboBoxModel;
import com.mathdroid.gui.main.EGlobalStrings;
import com.mathdroid.gui.main.abilities.optimizer.AbilityOptimizerFrame;
import com.mathdroid.model.EModelObserverEvent;
import com.mathdroid.model.IModelObserver;
import com.mathdroid.model.Model;
import com.mathdroid.model.abilities.Ability;
import com.mathdroid.model.abilities.AbilityTemplate;
import com.mathdroid.model.abilities.AbilityTemplates;
import com.mathdroid.model.abilities.optimizer.AbilityOptimizer;
import com.mathdroid.model.enums.ESpec;
import com.mathdroid.model.stats.ACharacterStats;
import com.mathdroid.model.stats.CharacterStatsFactory;
import com.mathdroid.persistence.Serializer;

/**
 * This panel gives the option to edit the used abilities.
 */
public final class AbilityPanel extends JPanel implements IModelObserver {
	/**
	 * Default UID.
	 */
	private static final long serialVersionUID = 1L;
	
	/**
	 * The representation of the ability template.
	 */
	private ComponentList abilities = new ComponentList();
	
	/**
	 * The space between components.
	 */
	private static final int SPACING = 10;
	
	/**
	 * The weight of components that want a lot of space.
	 */
	private static final int HUGE_WEIGHT = 100;
	
	/**
	 * The edge length of save/delete buttons.
	 */
	private static final int BUTTON_SIZE = 25;
	
	/**
	 * The constraints for components.
	 */
	private final GridBagConstraints c = new GridBagConstraints();
	
	/**
	 * Button to save ability templates.
	 */
	private JButton saveTemplate = null;
	
	/**
	 * Button to delete abilty templates.
	 */
	private JButton deleteTemplate = null;
	
	/**
	 * ComboBox to show ability templates.
	 */
	private JComboBox templates = null;
	
	/**
	 * The button to call the ability optimizer.
	 */
	private JButton optimize = new JButton("Optimize");
	
	/**
	 * ImageLoader to load icons.
	 */
	private final ImageLoader il = new ImageLoader();
	
	/**
	 * Serializer to save/load/delete templates.
	 */
	private final Serializer s = new Serializer();
	
	/**
	 * Create a new ability configuration panel.
	 */
	public AbilityPanel() {
		super();
		Model.registerObserver(this);
		this.setLayout(new GridBagLayout());
		
		this.buildComboBox();
		this.buildSaveButton();
		this.buildDeleteButton();
		this.buildOptimizeButton();
		this.buildAbilityList();
		
		this.setPreferredSize(new Dimension(0, 0));
	}
	
	/**
	 * Build the ComboBox that shows ability templates.
	 */
	private void buildComboBox() {
		this.c.fill = GridBagConstraints.HORIZONTAL;
		this.c.gridwidth = 1;
		this.c.gridheight = 1;
		this.c.weightx = HUGE_WEIGHT;
		this.c.weighty = 0;
		this.c.gridx = 0;
		this.c.gridy = 0;
		this.c.insets = new Insets(SPACING, SPACING, SPACING, SPACING);
		
		this.templates = new JComboBox(new DroidComboBoxModel());
		this.templates.setEditable(true);
		
		this.refreshComboBox();
		
		this.templates.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				loadTemplate();	
			}
		});
		
		this.templates.getEditor().getEditorComponent().addKeyListener(
				new KeyListener() {
					@Override
					public void keyPressed(final KeyEvent e) {
						if (e.getKeyCode() == KeyEvent.VK_ENTER) {
							templates.configureEditor(
									templates.getEditor(), saveTemplate());
						} else {
							deleteTemplate.setEnabled(false);
						}
					}
					@Override
					public void keyReleased(final KeyEvent e) { }
					@Override
					public void keyTyped(final KeyEvent e) { }
		});
		
		this.add(this.templates, this.c);
	}

	/**
	 * Build the button to delete templates.
	 */
	private void buildDeleteButton() {
		this.c.fill = GridBagConstraints.NONE;
		this.c.gridwidth = 1;
		this.c.gridheight = 1;
		this.c.weightx = 0;
		this.c.weighty = 0;
		this.c.gridx = 2;
		this.c.gridy = 0;
		this.c.insets = new Insets(SPACING, 0, SPACING, SPACING);
		
		this.deleteTemplate = new JButton(this.il.getImage("delete"));
		this.deleteTemplate.setToolTipText(
				EGlobalStrings.DELETE_TEMPLATE.toString());
		this.deleteTemplate.setPreferredSize(
				new Dimension(BUTTON_SIZE, BUTTON_SIZE));
		this.deleteTemplate.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				deleteTemplate();
				templates.configureEditor(templates.getEditor(), null);
			}
		});
		this.add(this.deleteTemplate, this.c);
	}
	
	/**
	 * Build the button to optimize the ability priorities.
	 */
	private void buildOptimizeButton() {
		this.c.fill = GridBagConstraints.HORIZONTAL;
		this.c.gridwidth = 3;
		this.c.gridheight = 1;
		this.c.weightx = HUGE_WEIGHT;
		this.c.weighty = 0;
		this.c.gridx = 0;
		this.c.gridy = 1;
		this.c.insets = new Insets(0, SPACING, SPACING, SPACING);
		
		this.optimize.setIcon(this.il.getImage("chart_column"));
		this.optimize.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				AbilityOptimizer ao = new AbilityOptimizer();
				AbilityOptimizerFrame f = new AbilityOptimizerFrame(ao);
				ao.addPropertyChangeListener(f);
				ao.execute();
			}
		});
		
		this.add(this.optimize, this.c);
	}

	/**
	 * Build the button to save templates.
	 */
	private void buildSaveButton() {
		this.c.fill = GridBagConstraints.NONE;
		this.c.gridwidth = 1;
		this.c.gridheight = 1;
		this.c.weightx = 0;
		this.c.weighty = 0;
		this.c.gridx = 1;
		this.c.gridy = 0;
		this.c.insets = new Insets(SPACING, 0, SPACING, SPACING);
		
		this.saveTemplate = new JButton(this.il.getImage("add"));
		this.saveTemplate.setToolTipText(
				EGlobalStrings.SAVE_TEMPLATE.toString());
		this.saveTemplate.setPreferredSize(
				new Dimension(BUTTON_SIZE, BUTTON_SIZE));
		this.saveTemplate.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {	
				templates.configureEditor(
						templates.getEditor(), saveTemplate());
			}
		});
		this.add(this.saveTemplate, this.c);
	}

	/**
	 * Build the list that shows the abilities.
	 */
	private void buildAbilityList() {
		this.c.fill = GridBagConstraints.BOTH;
		this.c.gridwidth = 3;
		this.c.gridheight = 1;
		this.c.weightx = HUGE_WEIGHT;
		this.c.weighty = HUGE_WEIGHT;
		this.c.gridx = 0;
		this.c.gridy = 2;
		this.c.insets = new Insets(0, 0, 0, 0);
		
		this.abilities.setComponentListCellRenderer(new AbilityCellRenderer());
		this.abilities.setComponentListModel(new AbilityListModel());
		
		this.add(this.abilities, this.c);
	}
	
	@Override
	public void modelChanged(final EModelObserverEvent e) {
		this.refresh();
		
		try {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					templates.configureEditor(
						templates.getEditor(), 
						Model.getProfile().getAbilities().toString());
				}
			});
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Refresh the view of the list.
	 */
	private void refresh() {
		this.abilities.removeElements();
		
		/* All abilities share the same character stats object */
		final ACharacterStats s = 
			new CharacterStatsFactory().get(Model.getESpec());
		
		for (Ability a : Model.getProfile().getAbilities()) {
			a.setCharStats(s);
			AbilityElement e = new AbilityElement(a);
			this.abilities.addElement(e);
		}
	}
	
	/**
	 * Refresh the ComboBox if templates were saved or deleted.
	 */
	private void refreshComboBox() {
		this.templates.removeAllItems();
		
		for (AbilityTemplate t : this.s.getAbilitySets(
				Model.getProfile().getSpec()).values()) {
			this.templates.addItem(t);
		}
	}
	
	/**
	 * Load a template if the editor content of the ComboBox is not a String.
	 */
	private void loadTemplate() {
		if (this.templates.getSelectedItem() instanceof AbilityTemplate) {
			Model.setAbilities(
					(AbilityTemplate) this.templates.getSelectedItem());
		}
	}

	/**
	 * Save the current template under the name shown in the ComboBox.
	 * @return The name of the saved template.
	 */
	private String saveTemplate() {
		Object editorItem = this.templates.getEditor().getItem();
		ESpec spec = Model.getProfile().getSpec();
		AbilityTemplates at = this.s.getAbilitySets(spec);
		AbilityTemplate t = null;
		String name = null;
		
		if (editorItem instanceof String && !editorItem.equals("")) {
			t = Model.getProfile().getAbilities();
			name = (String) editorItem;
			t.setName(name);
			at.put(t.toString(), t);
			this.s.serialize(at, spec.toString().toLowerCase());
			Model.setAbilities(t);
			this.deleteTemplate.setEnabled(true);
			this.showDialog(name);
		} else if (editorItem instanceof AbilityTemplate) {
			t = Model.getProfile().getAbilities();
			name = t.toString();
			at.put(name, t);
			this.s.serialize(at, spec.toString().toLowerCase());
			Model.setAbilities(t);
			this.deleteTemplate.setEnabled(true);
			this.showDialog(name);
		}
		
		this.refreshComboBox();
		return name;
	}
	
	/**
	 * Show the responding dialog after saving templates.
	 * @param name The template name.
	 */
	private void showDialog(final String name) {
		JOptionPane.showMessageDialog(Model.getDroidFrame(),
				"Saved ability template \"" + name + "\".\n"
					+ "This ability template is shared between all \"" 
					+ Model.getESpec().toString() + "\" profiles.",
				"Template saved", 
				JOptionPane.INFORMATION_MESSAGE);
	}

	/**
	 * Delete the template which has the name shown in the ComboBox.
	 */
	private void deleteTemplate() {
		Object editorItem = this.templates.getEditor().getItem();
		ESpec spec = Model.getProfile().getSpec();
		AbilityTemplates at = this.s.getAbilitySets(spec);
		
		if (editorItem instanceof String && !editorItem.equals("")) {
			at.remove((String) editorItem);
			this.s.serialize(at, spec.toString().toLowerCase());
		} else if (editorItem instanceof AbilityTemplate) {
			at.remove(((AbilityTemplate) editorItem).toString());
			this.s.serialize(at, spec.toString().toLowerCase());
		}
		
		this.refreshComboBox();
	}

}
